/*******************************************************************************
 * Copyright (c) 2000, 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Cagatay Kavukcuoglu <cagatayk@acm.org>
 *     - Fix for bug 10025 - Resizing views should not use height ratios
 *     Chris Gross chris.gross@us.ibm.com Bug 107443
 *******************************************************************************/
package org.eclipse.ui.internal;

import java.util.ArrayList;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.util.Geometry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
//import org.eclipse.swt.graphics.Cursor;
//import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IViewReference;
//import org.eclipse.ui.internal.dnd.AbstractDropTarget;
//import org.eclipse.ui.internal.dnd.DragUtil;
//import org.eclipse.ui.internal.dnd.IDragOverListener;
//import org.eclipse.ui.internal.dnd.IDropTarget;
import org.eclipse.ui.internal.dnd.SwtUtil;

/**
 * Abstract container that groups various layout
 * parts (possibly other containers) together as
 * a unit. Manages the placement and size of these
 * layout part based on the location of sashes within
 * the container.
 */
// RAP [rh] IDragOverListener disabled due to missing DnD support
public abstract class PartSashContainer extends LayoutPart implements
        ILayoutContainer/*, IDragOverListener */{

    protected Composite parent;

    protected ControlListener resizeListener;

    protected LayoutTree root;
    
    private Composite parentWidget;

    private LayoutPart zoomedPart;

    protected WorkbenchPage page;

    boolean active = false;
    boolean layoutDirty = false;

    /* Array of LayoutPart */
    protected ArrayList children = new ArrayList();
    
// RAP [rh] unused code: SashContainerDropTarget is disabled    
//    private SashContainerDropTarget dropTarget;

    protected static class RelationshipInfo {
        protected LayoutPart part;

        protected LayoutPart relative;

        protected int relationship;

        /**
         * Preferred size for the left child (this would be the size, in pixels of the child
         * at the time the sash was last moved)
         */
        protected int left;

        /**
         * Preferred size for the right child (this would be the size, in pixels of the child
         * at the time the sash was last moved)
         */
        protected int right;

        /**
         * Computes the "ratio" for this container. That is, the ratio of the left side over
         * the sum of left + right. This is only used for serializing PartSashContainers in 
         * a form that can be read by old versions of Eclipse. This can be removed if this
         * is no longer required. 
         * 
         * @return the pre-Eclipse 3.0 sash ratio
         */
        public float getRatio() {
            int total = left + right;
            if (total > 0) {
                return (float) left / (float) total;
            }
            
            return 0.5f;
        }
    }

// RAP [rh] Disabled AbstractDropTarget due to missing DnD support    
//    private class SashContainerDropTarget extends AbstractDropTarget {
//        private int side;
//
//        private int cursor;
//
//        private LayoutPart targetPart;
//
//        private LayoutPart sourcePart;
//
//        public SashContainerDropTarget(LayoutPart sourcePart, int side, int cursor, LayoutPart targetPart) {
//            this.setTarget(sourcePart, side, cursor, targetPart);
//        }
//        
//        public void setTarget(LayoutPart sourcePart, int side, int cursor, LayoutPart targetPart) {
//            this.side = side;
//            this.targetPart = targetPart;
//            this.sourcePart = sourcePart;
//            this.cursor = cursor;
//        }
//
//        public void drop() {
//            if (side != SWT.NONE) {
//                LayoutPart visiblePart = sourcePart;
//
//                if (sourcePart instanceof PartStack) {
//                    visiblePart = getVisiblePart((PartStack) sourcePart);
//                }
//
//                dropObject(getVisibleParts(sourcePart), visiblePart,
//                        targetPart, side);
//            }
//        }
//
//        public Cursor getCursor() {
//            return DragCursors.getCursor(DragCursors
//                    .positionToDragCursor(cursor));
//        }
//
//        public Rectangle getSnapRectangle() {
//            Rectangle targetBounds;
//
//            if (targetPart != null) {
//                targetBounds = DragUtil.getDisplayBounds(targetPart
//                        .getControl());
//            } else {
//                targetBounds = DragUtil.getDisplayBounds(getParent());
//            }
//
//            if (side == SWT.CENTER || side == SWT.NONE) {
//                return targetBounds;
//            }
//
//            int distance = Geometry.getDimension(targetBounds, !Geometry
//                    .isHorizontal(side));
//
//            return Geometry.getExtrudedEdge(targetBounds,
//                    (int) (distance * getDockingRatio(sourcePart, targetPart)),
//                    side);
//        }
//    }

    public PartSashContainer(String id, final WorkbenchPage page, Composite parentWidget) {
        super(id);
        this.page = page;
        this.parentWidget = parentWidget;
        resizeListener = new ControlAdapter() {
            public void controlResized(ControlEvent e) {
                resizeSashes();
            }
        };
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.ILayoutContainer#obscuredByZoom(org.eclipse.ui.internal.LayoutPart)
     */
    public boolean childObscuredByZoom(LayoutPart toTest) {
        LayoutPart zoomPart = getZoomedPart();
        
        if (zoomPart != null && toTest != zoomPart) {
            return true;
        }
        return isObscuredByZoom();
    }

// RAP [rh] unused code: was used by SashContainerDropTarget
//    /**
//     * Given an object associated with a drag (a PartPane or PartStack), this returns
//     * the actual PartPanes being dragged.
//     * 
//     * @param pane
//     * @return
//     */
//    private PartPane[] getVisibleParts(LayoutPart pane) {
//        if (pane instanceof PartPane) {
//            return new PartPane[] { (PartPane) pane };
//        } else if (pane instanceof PartStack) {
//            PartStack stack = (PartStack) pane;
//
//            LayoutPart[] children = stack.getChildren();
//            ArrayList result = new ArrayList(children.length);
//            for (int idx = 0; idx < children.length; idx++) {
//                LayoutPart next = children[idx];
//                if (next instanceof PartPane) {
//                    result.add(next);
//                }
//            }
//
//            return (PartPane[]) result.toArray(new PartPane[result.size()]);
//        }
//
//        return new PartPane[0];
//    }

    /**
     * Find the sashs around the specified part.
     */
    public void findSashes(LayoutPart pane, PartPane.Sashes sashes) {
        if (root == null) {
            return;
        }
        LayoutTree part = root.find(pane);
        if (part == null) {
			return;
		}
        part.findSashes(sashes);
    }

    /**
     * Add a part.
     */
    public void add(LayoutPart child) {
        if (child == null) {
			return;
		}

        addEnhanced(child, SWT.RIGHT, 0.5f, findBottomRight());
    }

    /**
     * Add a new part relative to another. This should be used in place of <code>add</code>. 
     * It differs as follows:
     * <ul>
     * <li>relationships are specified using SWT direction constants</li>
     * <li>the ratio applies to the newly added child -- not the upper-left child</li>
     * </ul>
     * 
     * @param child new part to add to the layout
     * @param swtDirectionConstant one of SWT.TOP, SWT.BOTTOM, SWT.LEFT, or SWT.RIGHT
     * @param ratioForNewPart a value between 0.0 and 1.0 specifying how much space will be allocated for the newly added part
     * @param relative existing part indicating where the new child should be attached
     */
    void addEnhanced(LayoutPart child, int swtDirectionConstant,
            float ratioForNewPart, LayoutPart relative) {
        int relativePosition = PageLayout
                .swtConstantToLayoutPosition(swtDirectionConstant);

        float ratioForUpperLeftPart;

        if (relativePosition == IPageLayout.RIGHT
                || relativePosition == IPageLayout.BOTTOM) {
            ratioForUpperLeftPart = 1.0f - ratioForNewPart;
        } else {
            ratioForUpperLeftPart = ratioForNewPart;
        }

        add(child, relativePosition, ratioForUpperLeftPart, relative);
    }

    /**
     * Add a part relative to another. For compatibility only. New code should use
     * addEnhanced, above.
     * 
     * @param child the new part to add
     * @param relationship one of PageLayout.TOP, PageLayout.BOTTOM, PageLayout.LEFT, or PageLayout.RIGHT
     * @param ratio a value between 0.0 and 1.0, indicating how much space will be allocated to the UPPER-LEFT pane
     * @param relative part where the new part will be attached
     */
    public void add(LayoutPart child, int relationship, float ratio,
            LayoutPart relative) {
        boolean isHorizontal = (relationship == IPageLayout.LEFT || relationship == IPageLayout.RIGHT);

        LayoutTree node = null;
        if (root != null && relative != null) {
            node = root.find(relative);
        }

        Rectangle bounds;
        if (getParent() == null) {
            Control control = getPage().getClientComposite();
            if (control != null && !control.isDisposed()) {
                bounds = control.getBounds();
            } else {
                bounds = new Rectangle(0, 0, 800, 600);
            }
            bounds.x = 0;
            bounds.y = 0;
        } else {
            bounds = getBounds();
        }

        int totalSize = measureTree(bounds, node, isHorizontal);

        int left = (int) (totalSize * ratio);
        int right = totalSize - left;

        add(child, relationship, left, right, relative);
    }

    static int measureTree(Rectangle outerBounds, LayoutTree toMeasure,
            boolean horizontal) {
        if (toMeasure == null) {
            return Geometry.getDimension(outerBounds, horizontal);
        }

        LayoutTreeNode parent = toMeasure.getParent();
        if (parent == null) {
            return Geometry.getDimension(outerBounds, horizontal);
        }

        if (parent.getSash().isHorizontal() == horizontal) {
            return measureTree(outerBounds, parent, horizontal);
        }

        boolean isLeft = parent.isLeftChild(toMeasure);

        LayoutTree otherChild = parent.getChild(!isLeft);
        if (otherChild.isVisible()) {
            int left = parent.getSash().getLeft();
            int right = parent.getSash().getRight();
            int childSize = isLeft ? left : right;

            int bias = parent.getCompressionBias();

            // Normalize bias: 1 = we're fixed, -1 = other child is fixed
            if (isLeft) {
                bias = -bias;
            }

            if (bias == 1) {
                // If we're fixed, return the fixed size
                return childSize;
            } else if (bias == -1) {

                // If the other child is fixed, return the size of the parent minus the fixed size of the
                // other child
                return measureTree(outerBounds, parent, horizontal)
                        - (left + right - childSize);
            }
            
            // Ensure we don't get a 'divide by zero'
            if ((left+right) == 0)
            	return 0;

            // Else return the size of the parent, scaled appropriately
            return measureTree(outerBounds, parent, horizontal) * childSize
                    / (left + right);
        }

        return measureTree(outerBounds, parent, horizontal);
    }

    protected void addChild(RelationshipInfo info) {
        LayoutPart child = info.part;

        children.add(child);

        if (root == null) {
            root = new LayoutTree(child);
        } else {
            //Add the part to the tree.
            int vertical = (info.relationship == IPageLayout.LEFT || info.relationship == IPageLayout.RIGHT) ? SWT.VERTICAL
                    : SWT.HORIZONTAL;
            boolean left = info.relationship == IPageLayout.LEFT
                    || info.relationship == IPageLayout.TOP;
            LayoutPartSash sash = new LayoutPartSash(this, vertical);
            sash.setSizes(info.left, info.right);
            if ((parent != null) && !(child instanceof PartPlaceholder)) {
				sash.createControl(parent);
			}
            root = root.insert(child, left, sash, info.relative);
        }

        childAdded(child);

        if (active) {
            child.createControl(parent);
            child.setVisible(true);
            child.setContainer(this);
            resizeChild(child);
        }

    }

    /**
     * Adds the child using ratio and position attributes
     * from the specified placeholder without replacing
     * the placeholder
     * 
     * FIXME: I believe there is a bug in computeRelation()
     * when a part is positioned relative to the editorarea.
     * We end up with a null relative and 0.0 for a ratio.
     */
    void addChildForPlaceholder(LayoutPart child, LayoutPart placeholder) {
        RelationshipInfo newRelationshipInfo = new RelationshipInfo();
        newRelationshipInfo.part = child;
        if (root != null) {
            newRelationshipInfo.relationship = IPageLayout.RIGHT;
            newRelationshipInfo.relative = root.findBottomRight();
            newRelationshipInfo.left = 200;
            newRelationshipInfo.right = 200;
        }

        // find the relationship info for the placeholder
        RelationshipInfo[] relationships = computeRelation();
        for (int i = 0; i < relationships.length; i++) {
            RelationshipInfo info = relationships[i];
            if (info.part == placeholder) {
                newRelationshipInfo.left = info.left;
                newRelationshipInfo.right = info.right;
                newRelationshipInfo.relationship = info.relationship;
                newRelationshipInfo.relative = info.relative;
            }
        }

        addChild(newRelationshipInfo);
        flushLayout();
    }

    /**
     * See ILayoutContainer#allowBorder
     */
    public boolean allowsBorder() {
        return true;
    }

    /**
     * Notification that a child layout part has been
     * added to the container. Subclasses may override
     * this method to perform any container specific
     * work.
     */
    protected void childAdded(LayoutPart child) {
    	if (isDeferred()) {
    		child.deferUpdates(true);
    	}
    }

    /**
     * Notification that a child layout part has been
     * removed from the container. Subclasses may override
     * this method to perform any container specific
     * work.
     */
    protected void childRemoved(LayoutPart child) {
    	if (isDeferred()) {
    		child.deferUpdates(false);
    	}
    }

    /**
     * Returns an array with all the relation ship between the
     * parts.
     */
    public RelationshipInfo[] computeRelation() {
        LayoutTree treeRoot = root;
        ArrayList list = new ArrayList();
        if (treeRoot == null) {
			return new RelationshipInfo[0];
		}
        RelationshipInfo r = new RelationshipInfo();
        r.part = treeRoot.computeRelation(list);
        list.add(0, r);
        RelationshipInfo[] result = new RelationshipInfo[list.size()];
        list.toArray(result);
        return result;
    }

    public void setActive(boolean isActive) {
        if (isActive == active) {
            return;
        }
        
        active = isActive;
        
        ArrayList children = (ArrayList) this.children.clone();
        for (int i = 0, length = children.size(); i < length; i++) {
            LayoutPart child = (LayoutPart) children.get(i);
            
            if (child instanceof PartStack) {
                PartStack stack = (PartStack) child;
                stack.setActive(isActive);
            }
        }
        
        if (isActive) {
            
            createControl(parentWidget);
            
            parent.addControlListener(resizeListener);

// RAP [rh] DnD support missing            
//            DragUtil.addDragTarget(parent, this);
//            DragUtil.addDragTarget(parent.getShell(), this);
            
            //ArrayList children = (ArrayList) this.children.clone();
            for (int i = 0, length = children.size(); i < length; i++) {
                LayoutPart child = (LayoutPart) children.get(i);
                child.setContainer(this);
                child.setVisible(zoomedPart == null || child == zoomedPart);
                if (!(child instanceof PartStack)) {
                    if (root != null) {
                        LayoutTree node = root.find(child);
                        if (node != null) {
                            node.flushCache();
                        }
                    }
                }
            }
            
            if (root != null) {
                //root.flushChildren();
                if (!isZoomed()) {
                    root.createControl(parent);
                }
            }
            
            resizeSashes();
        } else {
// RAP [rh] DnD support missing          
//            DragUtil.removeDragTarget(parent, this);
//            DragUtil.removeDragTarget(parent.getShell(), this);

            // remove all Listeners
            if (resizeListener != null && parent != null) {
                parent.removeControlListener(resizeListener);
            }

            if (children != null) {
                for (int i = 0, length = children.size(); i < length; i++) {
                    LayoutPart child = (LayoutPart) children.get(i);
                    child.setContainer(null);
                    if (child instanceof PartStack) {
                        child.setVisible(false);
                    }
                }
            }
            
            disposeSashes();
            
            //dispose();
        }
    }
    
    /**
     * @see LayoutPart#getControl
     */
    public void createControl(Composite parentWidget) {
        if (this.parent != null) {
			return;
		}

        parent = createParent(parentWidget);

        ArrayList children = (ArrayList) this.children.clone();
        for (int i = 0, length = children.size(); i < length; i++) {
            LayoutPart child = (LayoutPart) children.get(i);
            child.createControl(parent);
        }

    }

    /**
     * Subclasses override this method to specify
     * the composite to use to parent all children
     * layout parts it contains.
     */
    protected abstract Composite createParent(Composite parentWidget);

    /**
     * @see LayoutPart#dispose
     */
    public void dispose() {
        if (parent == null) {
			return;
		}

        if (children != null) {
            for (int i = 0, length = children.size(); i < length; i++) {
                LayoutPart child = (LayoutPart) children.get(i);

                // In PartSashContainer dispose really means deactivate, so we
                // only dispose PartTabFolders.
                if (child instanceof PartStack) {
					child.dispose();
				}
            }
        }
        disposeParent();
        this.parent = null;
    }

    /**
     * Subclasses override this method to dispose
     * of any swt resources created during createParent.
     */
    protected abstract void disposeParent();

    /**
     * Dispose all sashs used in this perspective.
     */
    public void disposeSashes() {
        if (root != null) {
            root.disposeSashes();
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.LayoutPart#setVisible(boolean)
     */
    public void setVisible(boolean makeVisible) {
        
        if (makeVisible == getVisible()) {
            return;
        }
        
        if (!SwtUtil.isDisposed(this.parent)) {
            this.parent.setEnabled(makeVisible);
        }
        super.setVisible(makeVisible);
        
        ArrayList children = (ArrayList) this.children.clone();
        for (int i = 0, length = children.size(); i < length; i++) {
            LayoutPart child = (LayoutPart) children.get(i);
            child.setVisible(makeVisible && (zoomedPart == null || child == zoomedPart));
        }
    }
    
    /**
     * Return the most bottom right part or null if none.
     */
    public LayoutPart findBottomRight() {
        if (root == null) {
			return null;
		}
        return root.findBottomRight();
    }

    /**
     * @see LayoutPart#getBounds
     */
    public Rectangle getBounds() {
        return this.parent.getBounds();
    }

    /**
     * @see ILayoutContainer#getChildren
     */
    public LayoutPart[] getChildren() {
        LayoutPart[] result = new LayoutPart[children.size()];
        children.toArray(result);
        return result;
    }

    /**
     * @see LayoutPart#getControl
     */
    public Control getControl() {
        return this.parent;
    }

    public LayoutTree getLayoutTree() {
        return root;
    }

    /**
     * For themes.
     * 
     * @return the current WorkbenchPage.
     */
    public WorkbenchPage getPage() {
        return page;
    }

    /**
     * Returns the composite used to parent all the
     * layout parts contained within.
     */
    public Composite getParent() {
        return parent;
    }

    protected boolean isChild(LayoutPart part) {
        return children.indexOf(part) >= 0;
    }

    /**
     * Returns whether this container is zoomed.
     */
    public boolean isZoomed() {
        return (zoomedPart != null);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.LayoutPart#forceLayout(org.eclipse.ui.internal.LayoutPart)
     */
    public void resizeChild(LayoutPart childThatChanged) {
    	if (root != null) {
    		LayoutTree tree = root.find(childThatChanged);
    		
    		if (tree != null) {
    			tree.flushCache();
    		}
    	}
    	
        flushLayout();

    }

    /**
     * Remove a part.
     */
    public void remove(LayoutPart child) {
        if (child == getZoomedPart()) {
			childRequestZoomOut();
		}

        if (!isChild(child)) {
			return;
		}

        children.remove(child);
        if (root != null) {
            root = root.remove(child);
        }
        childRemoved(child);

        if (active) {
            child.setVisible(false);
            child.setContainer(null);
            flushLayout();
        }
    }

	/* (non-Javadoc)
	 * @see org.eclipse.ui.internal.LayoutPart#forceLayout()
	 */
	public void flushLayout() {
		layoutDirty = true;
		super.flushLayout();
		
		if (layoutDirty) {
			resizeSashes();
		}
	}
	
    /**
     * Replace one part with another.
     */
    public void replace(LayoutPart oldChild, LayoutPart newChild) {

        if (!isChild(oldChild)) {
            return;
        }
        
        if (oldChild == getZoomedPart()) {
            if (newChild instanceof PartPlaceholder) {
                childRequestZoomOut();
            } else {
                zoomedPart.setZoomed(false);
                zoomedPart = newChild;
                zoomedPart.setZoomed(true);
            }
        }

        children.remove(oldChild);
        children.add(newChild);

        childAdded(newChild);

        if (root != null) {
            LayoutTree leaf = null;

            leaf = root.find(oldChild);
            if (leaf != null) {
                leaf.setPart(newChild);
            }
        }

        childRemoved(oldChild);
        if (active) {
            oldChild.setVisible(false);
            oldChild.setContainer(null);
            newChild.createControl(parent);
            newChild.setContainer(this);
            newChild.setVisible(zoomedPart == null || zoomedPart == newChild);
            resizeChild(newChild);
        }
    }

    private void resizeSashes() {
    	layoutDirty = false;
        if (!active) {
			return;
		}
        
        if (isZoomed()) {
            getZoomedPart().setBounds(parent.getClientArea());
        } else {
	        if (root != null) {
	            root.setBounds(parent.getClientArea());
	        }
        }
    }

    /**
     * Returns the maximum size that can be utilized by this part if the given width and
     * height are available. Parts can overload this if they have a quantized set of preferred 
     * sizes.
     * 
     * @param width available horizontal space (pixels)
     * @return returns a new point where point.x is <= availableWidth and point.y is <= availableHeight
     */
    public int computePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) {
        if (isZoomed()) {
            return getZoomedPart().computePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel);
        }
        
    	if (root != null) {
    		return root.computePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel);
    	}
    	    	
    	return preferredParallel;
    }
	
    public int getSizeFlags(boolean width) {
        if (isZoomed()) {
            return getZoomedPart().getSizeFlags(width);
        }
        
        if (root != null) {
            return root.getSizeFlags(width);
        }
        
        return 0;
    }
    
    /**
     * @see LayoutPart#setBounds
     */
    public void setBounds(Rectangle r) {
        this.parent.setBounds(r);
    }
    
    /**
     * Zoom in on a particular layout part.
     *
     * The implementation of zoom is quite simple.  When zoom occurs we create
     * a zoom root which only contains the zoom part.  We store the old
     * root in unzoomRoot and then active the zoom root.  When unzoom occurs
     * we restore the unzoomRoot and dispose the zoom root.
     *
     * Note: Method assumes we are active.
     */
    private void zoomIn(LayoutPart part) {
        // Sanity check.
        if (isZoomed()) {
			return;
		}

        // Hide the sashes
   		root.disposeSashes();
        
        // Make all parts invisible except for the zoomed part
        LayoutPart[] children = getChildren();
        for (int i = 0; i < children.length; i++) {
            LayoutPart child = children[i];
            child.setVisible(child == part);
        }
        
        zoomedPart = part;
                
        // Notify the part that it has been zoomed
    	part.setZoomed(true);
        
        // Remember that we need to trigger a layout
        layoutDirty = true;
    }

    /**
     * Returns the currently zoomed part or null if none
     * 
     * @return the currently zoomed part or null if none
     */
    public LayoutPart getZoomedPart() {
        return zoomedPart;
    }
    
    public void childRequestZoomIn(LayoutPart toZoom) {
        if (!SwtUtil.isDisposed(this.parent)) {
            this.parent.setRedraw(false);
        }
        try {
	        zoomIn(toZoom);
	        
	        requestZoomIn();
	        
	        if (layoutDirty) {
	            resizeSashes();
	        }
        } finally {
            if (!SwtUtil.isDisposed(this.parent)) {
                this.parent.setRedraw(true);
            }
        }
    }
    
    public void childRequestZoomOut() { 
        if (!SwtUtil.isDisposed(this.parent)) {
            this.parent.setRedraw(false);
        }
        try {
	        zoomOut();
	        
	        requestZoomOut();
	        
	        if (layoutDirty) {
	            resizeSashes();
	        }
        } finally {
            if (!SwtUtil.isDisposed(this.parent)) {
                this.parent.setRedraw(true);
            }
        }
    }
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.LayoutPart#setZoomed(boolean)
     */
    public void setZoomed(boolean isZoomed) {
        if (!isZoomed) {
            zoomOut();
        } else {
            if (!isZoomed()) {
                LayoutPart toZoom = pickPartToZoom();
                
                if (toZoom != null) {
                    zoomIn(toZoom);
                }
            }
        }
        super.setZoomed(isZoomed);
    }
    
    public LayoutPart pickPartToZoom() {
        return findBottomRight();
    }
    
    /**
     * Zoom out.
     *
     * See zoomIn for implementation details.
     * 
     * Note: Method assumes we are active.
     */
    private void zoomOut() {    	
        // Sanity check.
        if (!isZoomed()) {
			return;
		}
               
        LayoutPart zoomedPart = this.zoomedPart;
        this.zoomedPart = null;
        // Inform the part that it is no longer zoomed
        zoomedPart.setZoomed(false);
        
        // Make all children visible
        LayoutPart[] children = getChildren();
        for (int i = 0; i < children.length; i++) {
            LayoutPart child = children[i];
            
            child.setVisible(true);
        }
        
        // Recreate the sashes
        root.createControl(getParent());
        
        // Ensure that the part being un-zoomed will have its size refreshed.
        LayoutTree node = root.find(zoomedPart);
        node.flushCache();

        layoutDirty = true;
    }
    
// RAP [rh] Disabled implementation of IDragOverListener#drag()
//    /* (non-Javadoc)
//     * @see org.eclipse.ui.internal.dnd.IDragOverListener#drag(org.eclipse.swt.widgets.Control, java.lang.Object, org.eclipse.swt.graphics.Point, org.eclipse.swt.graphics.Rectangle)
//     */
//    public IDropTarget drag(Control currentControl, Object draggedObject,
//            Point position, Rectangle dragRectangle) {
//        if (!(draggedObject instanceof LayoutPart)) {
//            return null;
//        }
//
//        final LayoutPart sourcePart = (LayoutPart) draggedObject;
//
//        if (!isStackType(sourcePart) && !isPaneType(sourcePart)) {
//            return null;
//        }
//
//        boolean differentWindows = sourcePart.getWorkbenchWindow() != getWorkbenchWindow();
//        boolean editorDropOK = ((sourcePart instanceof EditorPane) && 
//        							sourcePart.getWorkbenchWindow().getWorkbench() == 
//        							getWorkbenchWindow().getWorkbench());
//        if (differentWindows && !editorDropOK) {
//            return null;
//        }
//
//        Rectangle containerBounds = DragUtil.getDisplayBounds(parent);
//        LayoutPart targetPart = null;
//        ILayoutContainer sourceContainer = isStackType(sourcePart) ? (ILayoutContainer) sourcePart
//                : sourcePart.getContainer();
//
//        // If this container has no visible children
//        if (getVisibleChildrenCount(this) == 0) {
//            return createDropTarget(sourcePart, SWT.CENTER, SWT.CENTER, null);            
//        }
//        
//        if (containerBounds.contains(position)) {
//
//            if (root != null) {
//                targetPart = root.findPart(parent.toControl(position));
//            }
//
//            if (targetPart != null) {
//                final Control targetControl = targetPart.getControl();
//
//                final Rectangle targetBounds = DragUtil
//                        .getDisplayBounds(targetControl);
//
//                int side = Geometry.getClosestSide(targetBounds, position);
//                int distance = Geometry.getDistanceFromEdge(targetBounds, position, side);
//               
//                // is the source coming from a standalone part
//                boolean standalone = (isStackType(sourcePart) 
//                		&& ((PartStack) sourcePart).isStandalone()) 
//                	|| (isPaneType(sourcePart) 
//                			&& ((PartPane) sourcePart).getStack()!=null
//                			&& ((PartPane) sourcePart).getStack().isStandalone());
//                
//                // Only allow dropping onto an existing stack from different windows
//                if (differentWindows && targetPart instanceof EditorStack) {
//                    IDropTarget target = targetPart.getDropTarget(draggedObject, position);
//                   	return target;
//                }
//                
//                // Reserve the 5 pixels around the edge of the part for the drop-on-edge cursor
//                if (distance >= 5 && !standalone) {
//                    // Otherwise, ask the part if it has any special meaning for this drop location
//                    IDropTarget target = targetPart.getDropTarget(draggedObject, position);
//                    if (target != null) {
//                        return target;
//                    }
//                }
//                
//                if (distance > 30 && isStackType(targetPart) && !standalone) {
//                    if (targetPart instanceof ILayoutContainer) {
//                        ILayoutContainer targetContainer = (ILayoutContainer)targetPart;
//                        if (targetContainer.allowsAdd(sourcePart)) {
//                            side = SWT.CENTER;
//                        }
//                    }
//                }
//                
//                // If the part doesn't want to override this drop location then drop on the edge
//                
//                // A "pointless drop" would be one that will put the dragged object back where it started.
//                // Note that it should be perfectly valid to drag an object back to where it came from -- however,
//                // the drop should be ignored.
//
//                boolean pointlessDrop = isZoomed();
//
//                if (sourcePart == targetPart) {
//                    pointlessDrop = true;
//                }
//
//                if ((sourceContainer != null)
//                        && (sourceContainer == targetPart)
//                        && getVisibleChildrenCount(sourceContainer) <= 1) {
//                    pointlessDrop = true;
//                }
//
//                if (side == SWT.CENTER
//                        && sourcePart.getContainer() == targetPart) {
//                    pointlessDrop = true;
//                }
//
//                int cursor = side;
//
//                if (pointlessDrop) {
//                    side = SWT.NONE;
//                    cursor = SWT.CENTER;
//                }
//
//                return createDropTarget(sourcePart, side, cursor, targetPart);
//            }
//        } else {
//        	// We only allow dropping into a stack, not creating one
//        	if (differentWindows)
//        		return null;
//        	
//            int side = Geometry.getClosestSide(containerBounds, position);
//
//            boolean pointlessDrop = isZoomed();
//
//            if ((isStackType(sourcePart) && sourcePart.getContainer() == this)
//                    || (sourcePart.getContainer() != null
//                       && isPaneType(sourcePart) 
//                       && getVisibleChildrenCount(sourcePart.getContainer()) <= 1) 
//                       && ((LayoutPart)sourcePart.getContainer()).getContainer() == this) {
//                if (root == null || getVisibleChildrenCount(this) <= 1) {
//                    pointlessDrop = true;
//                }
//            }
//
//            int cursor = Geometry.getOppositeSide(side);
//
//            if (pointlessDrop) {
//                side = SWT.NONE;
//            }
//
//            return createDropTarget(sourcePart, side, cursor, null);
//        }
//
//        return null;
//    }

// RAP [rh] DnD support missing    
//    /**
//     * @param sourcePart
//     * @param targetPart
//     * @param side
//     * @param cursor
//     * @return
//     * @since 3.1
//     */
//    private SashContainerDropTarget createDropTarget(final LayoutPart sourcePart, int side, int cursor, LayoutPart targetPart) {
//        if (dropTarget == null) {
//            dropTarget = new SashContainerDropTarget(sourcePart, side, cursor,
//                targetPart);
//        } else {
//            dropTarget.setTarget(sourcePart, side, cursor, targetPart);
//        }
//        return dropTarget;
//    }

    /**
     * Returns true iff this PartSashContainer allows its parts to be stacked onto the given
     * container.
     * 
     * @param container
     * @return
     */
    public abstract boolean isStackType(LayoutPart toTest);

    public abstract boolean isPaneType(LayoutPart toTest);

    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.PartSashContainer#dropObject(org.eclipse.ui.internal.LayoutPart, org.eclipse.ui.internal.LayoutPart, int)
     */
    protected void dropObject(PartPane[] toDrop, LayoutPart visiblePart,
            LayoutPart targetPart, int side) {
        getControl().setRedraw(false);

        // Targetpart is null if there isn't a part under the cursor (all the parts are
        // hidden or the container is empty). In this case, the actual side doesn't really
        // since we'll be the only visible container and will fill the entire space. However,
        // we can't leave it as SWT.CENTER since we can't stack if we don't have something
        // to stack on. In this case, we pick SWT.BOTTOM -- this will insert the new pane
        // below any currently-hidden parts.
        if (targetPart == null && side == SWT.CENTER) {
            side = SWT.BOTTOM;
        }
        
        if (side == SWT.CENTER) {
            if (isStackType(targetPart)) {
                PartStack stack = (PartStack) targetPart;
                for (int idx = 0; idx < toDrop.length; idx++) {
                    PartPane next = toDrop[idx];
                    stack(next, stack);
                }
            }
        } else {
            PartStack newPart = createStack();

            // if the toDrop array has 1 item propogate the stack
            // appearance
            if (toDrop.length == 1 && toDrop[0].getStack()!=null) {
                toDrop[0].getStack().copyAppearanceProperties(newPart);                
            }
            
            for (int idx = 0; idx < toDrop.length; idx++) {
                PartPane next = toDrop[idx];
                stack(next, newPart);
            }

            addEnhanced(newPart, side, getDockingRatio(newPart, targetPart),
                    targetPart);
        }

        if (visiblePart != null) {
            setVisiblePart(visiblePart.getContainer(), visiblePart);
        }

        getControl().setRedraw(true);
        
        if (visiblePart != null) {
            visiblePart.setFocus();
        }
    }

    protected abstract PartStack createStack();

    public void stack(LayoutPart newPart, ILayoutContainer container) {
        getControl().setRedraw(false);

		// Only deref the part if it is being referenced in -this- perspective
        Perspective persp = page.getActivePerspective();
        PerspectiveHelper pres = (persp != null) ? persp.getPresentation() : null;
    	if (pres != null && newPart instanceof ViewPane) {
    		ViewPane vp = (ViewPane) newPart;
    		IViewReference vRef = vp.getViewReference();
    		LayoutPart fpp = pres.findPart(vRef.getId(), vRef.getSecondaryId());
    		
    		// 'findPart' won't find fast views that don't exist in the main presentation
    		if (fpp != null || persp.isFastView(vRef)) {
    	        // Remove the part from old container.
    	        derefPart(newPart);
    		}
    	}
    	else {
	        // Remove the part from old container.
	        derefPart(newPart);
    	}
    	
        // Reparent part and add it to the workbook
        newPart.reparent(getParent());
        container.add(newPart);
        getControl().setRedraw(true);

    }

    /**
     * @param container
     * @param visiblePart
     */
    protected abstract void setVisiblePart(ILayoutContainer container,
            LayoutPart visiblePart);

    /**
     * @param container
     * @return
     */
    protected abstract LayoutPart getVisiblePart(ILayoutContainer container);

    /**
     * @param sourcePart
     */
    protected void derefPart(LayoutPart sourcePart) {
        ILayoutContainer container = sourcePart.getContainer();
        if (container != null) {
            container.remove(sourcePart);
        }

        if (container instanceof LayoutPart) {
            if (isStackType((LayoutPart) container)) {
                PartStack stack = (PartStack) container;
                if (stack.getChildren().length == 0) {
                    remove(stack);
                    stack.dispose();
                }
            }
        }
    }

    protected int getVisibleChildrenCount(ILayoutContainer container) {
        // Treat null as an empty container
        if (container == null) {
            return 0;
        }

        LayoutPart[] children = container.getChildren();

        int count = 0;
        for (int idx = 0; idx < children.length; idx++) {
            if (!(children[idx] instanceof PartPlaceholder)) {
                count++;
            }
        }

        return count;
    }

    protected float getDockingRatio(LayoutPart dragged, LayoutPart target) {
        return 0.5f;
    }

    /**
     * Writes a description of the layout to the given string buffer.
     * This is used for drag-drop test suites to determine if two layouts are the
     * same. Like a hash code, the description should compare as equal iff the
     * layouts are the same. However, it should be user-readable in order to
     * help debug failed tests. Although these are english readable strings,
     * they should not be translated or equality tests will fail.
     * 
     * @param buf
     */
    public void describeLayout(StringBuffer buf) {
        if (root == null) {
            return;
        }

        if (isZoomed()) {
            buf.append("zoomed "); //$NON-NLS-1$
            root.describeLayout(buf);
        } else {
            buf.append("layout "); //$NON-NLS-1$
            root.describeLayout(buf);
        }
    }

    /**
     * Adds a new child to the container relative to some part
     * 
     * @param child
     * @param relationship
     * @param left preferred pixel size of the left/top child
     * @param right preferred pixel size of the right/bottom child
     * @param relative relative part
     */
    void add(LayoutPart child, int relationship, int left, int right,
            LayoutPart relative) {

        if (child == null) {
			return;
		}
        if (relative != null && !isChild(relative)) {
			return;
		}
        if (relationship < IPageLayout.LEFT
                || relationship > IPageLayout.BOTTOM) {
			relationship = IPageLayout.LEFT;
		}

        // store info about relative positions
        RelationshipInfo info = new RelationshipInfo();
        info.part = child;
        info.relationship = relationship;
        info.left = left;
        info.right = right;
        info.relative = relative;
        addChild(info);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.ILayoutContainer#isZoomed(org.eclipse.ui.internal.LayoutPart)
     */
    public boolean childIsZoomed(LayoutPart toTest) {
        return toTest == getZoomedPart();
    }
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.ILayoutContainer#allowsAutoFocus()
     */
    public boolean allowsAutoFocus() {
        return true;
    }
    
    /* (non-Javadoc)
	 * @see org.eclipse.ui.internal.LayoutPart#startDeferringEvents()
	 */
	protected void startDeferringEvents() {
		super.startDeferringEvents();
		
		LayoutPart[] deferredChildren = (LayoutPart[]) children.toArray(new LayoutPart[children.size()]);
		for (int i = 0; i < deferredChildren.length; i++) {
			LayoutPart child = deferredChildren[i];
			
			child.deferUpdates(true);
		}
	}
    
	/* (non-Javadoc)
	 * @see org.eclipse.ui.internal.LayoutPart#handleDeferredEvents()
	 */
	protected void handleDeferredEvents() {
		super.handleDeferredEvents();
		
		LayoutPart[] deferredChildren = (LayoutPart[]) children.toArray(new LayoutPart[children.size()]);
		for (int i = 0; i < deferredChildren.length; i++) {
			LayoutPart child = deferredChildren[i];
		
			child.deferUpdates(false);
		}			
	}
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.LayoutPart#testInvariants()
     */
    public void testInvariants() {
        super.testInvariants();

        // If we have a parent container, ensure that we are displaying the zoomed appearance iff 
        // our parent is zoomed in on us
        if (getContainer() != null) {
            Assert.isTrue((getZoomedPart() != null) == (getContainer().childIsZoomed(this)));
        }
        
        LayoutPart[] childArray = getChildren();

        for (int idx = 0; idx < childArray.length; idx++) {
            childArray[idx].testInvariants();
        }
        
        // If we're zoomed, ensure that we're actually zoomed into one of our children
        if (isZoomed()) {
            Assert.isTrue(children.contains(zoomedPart));
        }
    }
}
